/*
 * TacticalAdvisor.java
 *
 * Created on September 9, 2006, 10:15 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package zh;
import java.awt.font.TextAttribute;
import java.awt.geom.Point2D;
import java.text.AttributedString;
import java.util.logging.Level;
import robocode.*;
import zh.logging.MyLogger;

/**
 * In charge of aiming the gun, firing, and mantaining a lock on the current target.
 * @author zheismann
 */
public class TacticalAdvisor
{
    private static MyLogger logger = MyLogger.getLogger(TacticalAdvisor.class.getName());
    static { logger.setEnabled(true); logger.setLevel(Level.FINER); }
    
    private UnderDog robot;
    private static final String ZERO_EVENT = "ZeroRadarSweepLeft";
    private boolean startGameProceduresExecuted = false;
    private RadarTurnCompleteCondition radarTurnCompleteCondition;
    private boolean addEventExecuted = false;
    private long lastTimeScanRadarCalled;
    private Point2D lastTargetedLocation;
    private double lastAngleDiff;
    
    public TacticalAdvisor(UnderDog r)
    {
        robot = r;
    }
    
    public void makeDecision()
    {
        if (!startGameProceduresExecuted)
        {
            radarTurnCompleteCondition = new RadarTurnCompleteCondition(robot);
            robot.addCustomEvent(radarTurnCompleteCondition);
            startGameProceduresExecuted = true;
            logger.log("makeDecision(): startGameProceduresExecuted " + startGameProceduresExecuted);
        }
        if (!EnemyManager.getInstance().hasEnemy() && robot.getRadarTurnRemaining() == 0.0)
        {
            logger.log("makeDecision(): !EnemyManager.getInstance().hasEnemy()");
            robot.setTurnRadarRight(360);
        }
        if (robot.getTime()-lastTimeScanRadarCalled > 40)
        {
            new Exception("**********$********").printStackTrace();
        }
    }
    
    public void processEvent(CustomEvent event)
    {
        if (event.getCondition() instanceof RadarTurnCompleteCondition)
        {
            scanRadar();
        }
    }
    
    public void processEvent(DeathEvent event)
    {
        reset();
    }
    public void processEvent(WinEvent event)
    {
        reset();
    }
    
    private void reset()
    {
        startGameProceduresExecuted = false;
        robot.removeCustomEvent(radarTurnCompleteCondition);
    }
    
    public void processEvent(ScannedRobotEvent event)
    {
        if (robot.getGunTurnRemaining() == 0.0)
        {
            handleAimingAndFiring(event);
        }
    }
    
    double CURRENT_RADAR_TURN_DIRECTION = 1.0;
    /**
     * Take care of positioning the radar.
     */
    private void scanRadar()
    {
        EnemyManager em = EnemyManager.getInstance();
       
        double maxRadarTurnAngle = 0.0;
        for (Enemy enemy : em.getAllEnemies())
        {
            double angleFromRadarToRobot = Utils.normalRelativeAngle(robot.getHeading() + enemy.getBearing() - robot.getRadarHeading());
            if (Math.abs(angleFromRadarToRobot) > Math.abs(maxRadarTurnAngle))
            {
                maxRadarTurnAngle = angleFromRadarToRobot;
            }
        }
        // scan 180 by default if no enemies have been found yet
        double radarTurn = CURRENT_RADAR_TURN_DIRECTION * 180;
        if (em.getAllEnemies().size() == 1 && isRecent(em.getClosestEnemy()))
        {
            // compensate for movement since the last scan
            radarTurn = maxRadarTurnAngle + (27 * (maxRadarTurnAngle >= 0.0 ? 1.0 : -1.0));
        }
        logger.log(Level.FINE, "radarTurn: " + radarTurn);
        robot.setTurnRadarRight(radarTurn);
        CURRENT_RADAR_TURN_DIRECTION = (radarTurn >= 0.0 ? 1.0 : -1.0);
        lastTimeScanRadarCalled = robot.getTime();
    }
    
    private boolean isRecent(Enemy enemy)
    {
        if ((robot.getTime() - enemy.getLastUpdatedTime()) >= 15)
        { 
            logger.log(robot.getTime() + " " + enemy.getLastUpdatedTime());
        }
        return (robot.getTime() - enemy.getLastUpdatedTime()) < 15;
    }

    
    /**
     * Take care of aiming the gun, and determining if we should fire.
     */
    private void handleAimingAndFiring(ScannedRobotEvent event)
    {
        String enemyName = event.getName();
        Enemy closestEnemy = EnemyManager.getInstance().getClosestEnemy();
        // don't change your aim if we are not scanning the closest enemy.
        if (enemyName == null || closestEnemy == null || !enemyName.equals(closestEnemy.getName()))
        {
            return;
        }
        double distance = event.getDistance();
        double otherRobotsHeading = event.getHeading();
        double enemyVelocity = event.getVelocity();
        Enemy enemy = EnemyManager.getInstance().getEnemy(enemyName);
        double power = 0.1; // velocity ==?
        power = (distance < 450.0) ? 0.3 : power; // velocity == ?
        power = (distance < 350.0) ? 0.5 : power; // velocity == ?
        power = (distance < 250.0) ? 1.0 : power; // velocity == 17.0
        power = (distance < 200.0) ? 2.0 : power; // velocity == 14.0
        power = (distance < 150.0) ? 1.5 : power; // velocity == ?
        power = (distance < 75.0) ? 2.5 : power; // velocity == ?

        double angleDiff = determineNewGunHeading(distance, enemyVelocity, power, otherRobotsHeading, enemy.getLocation());
        robot.setTurnGunRight(angleDiff);
        logger.log(Level.FINE, "angleDiff: " + angleDiff);
        if (robot.getGunHeat() <= 0.5 && robot.getEnergy() >= 3.1) 
        {
            Bullet bullet = robot.setFireBullet(power);
            // dont think this is working quite yet as it seems to be called more than
            // the number of times a bullet is actually being fired
            enemy.addBulletFiredAtMe();
        }
    }
    
    private double velocityOfBullet(double powerOfBullet)
    {
        return 20.0 - (3 * powerOfBullet);
    }


    /**
     * @currentLocation my Robot's current location
     * @targetsLocation
     * @targetsDestination the predicted targetsDestination of the robot we are aiming for
     */
    private static double determineDegreeAngleToPoint(Point2D currentLocation, Point2D targetsDestination)
    {
        double c = currentLocation.distance(targetsDestination);
        Point2D trueNorthPoint = new Point2D.Double(currentLocation.getX(), currentLocation.getY() + c);
        double b = currentLocation.distance(trueNorthPoint);
        double a = trueNorthPoint.distance(targetsDestination);
        // law of cosines 
        double degreeAngle = Math.toDegrees(Math.acos(((b*b + c*c - a*a)/(2*b*c))));
        return degreeAngle;
    }  
    
    private double determineNewGunHeading(double distanceToTarget, double targetsVelocity, double powerOfBullet, 
                                          double targetsHeading, Point2D targetsLocation)
    {
        Point2D currentLocation = robot.getLocation();
        double currentGunHeading = robot.getGunHeading();
        StringBuilder sb = new StringBuilder("targetsVelocity: " + targetsVelocity + "  targetsHeading: " + targetsHeading + "  powerOfBullet: " + powerOfBullet + "  ");
        LinearTargetingSystem t = new LinearTargetingSystem();
        Point2D targetsNextLocation = t.predictNewLocation(currentLocation, targetsLocation, targetsHeading, targetsVelocity, powerOfBullet);
        lastTargetedLocation = targetsNextLocation;
        sb.append("\ntargetsNextLocation: " + targetsNextLocation);
        logger.log(Level.FINE, sb.toString());
        double angle = determineDegreeAngleToPoint(currentLocation, targetsNextLocation);
        if (lastTargetedLocation.getX() < robot.getX())
        {
            angle = 360.0 - angle;
        }
        double angleDiff = angle - robot.getGunHeading();
        lastAngleDiff = angleDiff;
        logger.log(Level.FINE, "angleDiff: " + angleDiff);
        if (angleDiff > 90.0 || angleDiff < -90.0)
        {
            angleDiff = Utils.normalRelativeAngle(angleDiff);
        }
        
        return angleDiff;
    }
    
    public void processEvent(HitRobotEvent event)
    {
        double turnGunAmt = Utils.normalRelativeAngle(event.getBearing() + robot.getHeading() - robot.getGunHeading());
        robot.setTurnGunRight(turnGunAmt);
        robot.setFire(3);
    }
    
    public void processEvent(BulletMissedEvent event)
    {
    }

    public void processEvent(RobotDeathEvent event)
    {
        // search for new target if one exists
        robot.setTurnRadarRight(360);
    }
    
    public void processEvent(HitByBulletEvent event)
    {
        // TODO
    }

    public void onPaint(java.awt.Graphics2D g)
    {
        Enemy enemy = EnemyManager.getInstance().getClosestEnemy();
        if (enemy != null)
        {
            Point2D loc = enemy.getLocation();
            g.setColor(java.awt.Color.WHITE);
            g.drawLine((int)robot.getX(), (int)robot.getY(), (int)loc.getX(), (int)loc.getY());
            g.setColor(java.awt.Color.GREEN);
            double gunHeadingRads = robot.getGunHeadingRadians();

            double dist = enemy.getDistance();
            g.drawLine((int)robot.getX(), (int)robot.getY(), (int)(Math.toDegrees(Math.sin(gunHeadingRads))*dist), (int)(Math.toDegrees(Math.cos(gunHeadingRads))*dist));
        }
        
        if (lastTargetedLocation != null)
        {
            g.setColor(java.awt.Color.RED);
            g.drawLine((int)robot.getX(), (int)robot.getY(), (int)lastTargetedLocation.getX(), (int)lastTargetedLocation.getY());
            double angle = 0.0;
            g.setColor(java.awt.Color.WHITE);
            AttributedString attrString = new AttributedString("lastAngleDiff: " + lastAngleDiff);
            attrString.addAttribute(TextAttribute.WIDTH, TextAttribute.WIDTH_EXTENDED);
            int halfHeight = (int)(robot.getBattleFieldHeight()/2.0);
            int halfWidth = (int)(robot.getBattleFieldWidth()/2.0);
            attrString.addAttribute(TextAttribute.WIDTH, TextAttribute.WIDTH_EXTENDED);
            g.drawString(attrString.getIterator(), halfWidth, halfHeight);
        }
    }    
    
}




